package com.zsc.boot.service.impl;

import java.util.*;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.zsc.boot.bean.User;
import com.zsc.boot.bean.vo.UserVO;
import com.zsc.boot.common.ErrorType;
import com.zsc.boot.exception.BusinessException;
import com.zsc.boot.service.UserService;
import com.zsc.boot.mapper.UserMapper;
import com.zsc.boot.utils.AlgorithmUtils;
import com.zsc.boot.utils.JsonToStringUtils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.zsc.boot.contant.UserContant.ADMIN_ROLE;
import static com.zsc.boot.contant.UserContant.USER_LOGIN_STATE;

/**
 *
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private UserMapper userMapper;

    //盐值:加密密码
    private static final String SALT = "hines";


    /**
     * 注册逻辑
     *
     * @param userAccount   用户名
     * @param userPassword  密码
     * @param checkPassword 校验码
     * @return
     */
    @Override
    public long UserRegister(String username, String userAccount, String userPassword, String checkPassword) {
        /*校验*/

        //1、使用lang3的方法快速判断这三个字段是不是空的
        if (StringUtils.isAnyBlank(username, userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }

        //2、根据所写的注册逻辑判断

        /*账户不小于4*/
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorType.PARAMS_ERROR,"账号不能小于4位");
        }

        /*密码不小于8*/
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorType.PARAMS_ERROR,"密码不能小于8位");
        }

        /*账户不能包含特殊字符*/
        String vaildPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";//这里表示特殊字符
        Matcher matcher = Pattern.compile(vaildPattern).matcher((userAccount));
        if (matcher.find()) { //找到特殊字符就返回-1
            throw new BusinessException(ErrorType.PARAMS_ERROR,"不能包含特殊字符");

        }

        /*校验密码相同*/
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorType.PARAMS_ERROR,"密码不一致");
        }

        /*账号不能重复*/
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);//这里的userAccount是上面形参传过来的，这条语句表示根据这个userAccount查询有多少条语句
        //这里的this是指当前UserServiceImpl的对象
        long count = this.count(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorType.PARAMS_ERROR,"账号不能重复");
        }

        /*密码加密*/
        String newPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());

        /*插入数据到数据库中*/
        User user = new User();
        user.setUsername(username);
        user.setUserAccount(userAccount);
        user.setUserPassword(newPassword);
        boolean result = this.save(user);
        //如果插入不成功就抛出异常
        if (!result) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }
        return user.getId(); //因为返回值是long型的id（注册了一个账号就返回一个id）
    }


    /**
     * 登录逻辑
     *
     * @param userAccount  用户名
     * @param userPassword 密码
     * @return
     */
    @Override
    public User UserLogin(String userAccount, String userPassword, HttpServletRequest request) {
        /*校验*/

        //1、使用lang3的方法快速判断这三个字段是不是空的
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorType.NULL_ERROR);
        }

        //2、根据所写的注册逻辑判断

        /*账户不小于4*/
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }

        /*密码不小于8*/
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }

        /*账户不能包含特殊字符*/
        String vaildPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";//这里表示特殊字符
        Matcher matcher = Pattern.compile(vaildPattern).matcher((userAccount));
        if (matcher.find()) { //找到特殊字符就返回-1
            throw new BusinessException(ErrorType.PARAMS_ERROR);

        }

        /*密码加密*/
        String newPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());


        /*查询用户是否存在*/
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);//这里的userAccount是上面形参传过来的，这俄格语句表示根据这个userAccount查询有多少条语句
        queryWrapper.eq("userPassword", newPassword);
        User user = this.getOne(queryWrapper);
        if (user == null) {
//            throw new BusinessException(ErrorType.PARAMS_ERROR);
            return null;
        }

        //3 对返回的用户信息进行脱敏(将password,updateTime,isDelete删除)
        User safeuser = getSafeUser(user);


        //4 记录用户的登录态(session记录)
        request.getSession().setAttribute(USER_LOGIN_STATE, safeuser);

        return safeuser;
    }

    /**
     * 脱敏方法
     *
     * @param user
     * @return
     */
    @Override
    public User getSafeUser(User user) {
        if (user == null) {
            throw new BusinessException(ErrorType.NULL_ERROR);
        }
        User safeUser = new User();
        safeUser.setId(user.getId());
        safeUser.setUsername(user.getUsername());
        safeUser.setUserAccount(user.getUserAccount());
        safeUser.setAvatar(user.getAvatar());
        safeUser.setGender(user.getGender());
        safeUser.setPhone(user.getPhone());
        safeUser.setProfile(user.getProfile());
        safeUser.setEmail(user.getEmail());
        safeUser.setUserStatus(user.getUserStatus());
        safeUser.setRole(user.getRole());
        safeUser.setCreateTime(user.getCreateTime());
        safeUser.setTags(user.getTags());
        safeUser.setFriendsIds(user.getFriendsIds());
        return safeUser;
    }

    /**
     * 注销逻辑
     *
     * @param request
     */
    @Override
    public int UserLogout(HttpServletRequest request) {
        //之前登录时候定义的session的键为常量
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }

    /**
     * 根据标签搜索用户【使用内存的方式进行搜索查询】
     *
     * @param tagNameList：用户要拥有的标签
     * @return
     */
    @Override
    public List<User> searchUserByTags(List<String> tagNameList) {
        //判断是否为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> userList = userMapper.selectList(queryWrapper);
        Gson gson = new Gson();

        return userList.stream().filter((user) -> {
            String tagsStr = user.getTags();
            if (StringUtils.isBlank(tagsStr)) {
                return false;
            }
            //使用Gson将字符串转化为json
            Set<String> tempTagNameSet = gson.fromJson(tagsStr, new TypeToken<Set<String>>() {
            }.getType());
            tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());
            for (String tagName : tagNameList) {
                if (!tempTagNameSet.contains(tagName)) {
                    return false;
                }
            }
            return true;
        }).map(this::getSafeUser).collect(Collectors.toList());
    }

    /**
     * 用户修改个人信息的功能
     *
     * @param user：用户所要修改的信息
     * @param loginUser：当登录的用户信息
     * @return
     */
    @Override
    public int updateUser(User user, User loginUser) {
        /* 前端的user页面需要修改用户的信息，那如何判断用户是否能修改呢
           1、用户只能修改自己的信息
           2、管理员可以修改任何用户的信息
        */

        /*如果当前登录的用户是管理员，允许更新任何用户*/
        if (isAdmin(loginUser)) {
            //那如何进行更新修改呢？理论上只需要userMapper.updateById(user)这样就能更新数据库中的
            //mybatis会自动根据user中的id属性查找到相对应的用户进行更新
            //但是我们要进行更为严密的判断，需要先判断更新过来的用户id是否为空，并且这个用户是否存在。
            long id = user.getId();
            if (id <= 0) {
                throw new BusinessException(ErrorType.PARAMS_ERROR);
            }
            User olderUser = userMapper.selectById(id);
            if (olderUser == null) {
                throw new BusinessException(ErrorType.NULL_ERROR);
            }
            return userMapper.updateById(user);
        }

        /*如果当前用户修改的自己的信息*/
        long id = user.getId();
        if (id <= 0) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }
        //如果过所要修改的用户信息的id等于登录用户的id则是用一个人
        if (id == loginUser.getId()) {
            User olderUser = userMapper.selectById(id);
            if (olderUser == null) {
                throw new BusinessException(ErrorType.NULL_ERROR);
            }
            return userMapper.updateById(user);
        } else {
            throw new BusinessException(ErrorType.NO_AUTH);
        }
    }

    /**
     * 获取当前登录用户的信息
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        if (request == null) {
            return null;
        }

        //获取当前登录用户的信息
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User loginUser = (User) userObj;
        //如果当前登录的用户信息为空，则用户修改的不是自己的信息，抛出异常无权限。
        if (loginUser == null) {
            throw new BusinessException(ErrorType.NO_AUTH);
        }
        return loginUser;
    }

    /**
     * 判断是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        //这里的user == null的判断是为了防止session的值没有去到，出现空指针异常
        if (user == null || user.getRole() != ADMIN_ROLE) {
            return false;
        }
        return true;
    }

    /**
     * 判断是否为管理员【重载】
     *
     * @param loginUser
     * @return
     */
    @Override
    public boolean isAdmin(User loginUser) {
        //这里的user == null的判断是为了防止session的值没有去到，出现空指针异常
        if (loginUser == null || loginUser.getRole() != ADMIN_ROLE) {
            return false;
        }
        return true;
    }

    /**
     * 匹配最佳用户
     * @param num
     * @param loginUser
     * @return
     */
    @Override
    public List<UserVO> matcherUser(long num, User loginUser) {
        //1、首先先查出只包含id和tags这两列的所有用户信息，并且过滤掉标签为空的
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.select("id","tags");
        queryWrapper.isNotNull("tags");
        List<User> userList = this.list(queryWrapper);

        //2、取出当前登录的用户的标签,并且如果是json格式的标签就转成字符串
        String loginUserTags = loginUser.getTags();
        Gson gson = new Gson();
        List<String> loginUserTagList = gson.fromJson(loginUserTags, new TypeToken<List<String>>() {
        }.getType());

        //3、使用List中存放pair的数据结构存储 pair中存储 key：用户信息（user），value：相似度 【用户列表下标 => 相似度】
        List<Pair<User,Long>> list = new ArrayList<>();

        //依次计算当前用户和与所有用户的相似度
        for(int i = 0; i < userList.size();i++){

            //将取出user转成userVO
            User user = userList.get(i);

            //无标签或者当前用户为自己就不进行计算
            if(user.getTags() == null || user.getId() == loginUser.getId()){
                continue;
            }

            //将当前遍历的标签的json格式转化成字符串
            String tags = user.getTags();
            List<String> userTagList = gson.fromJson(tags, new TypeToken<List<String>>() {
            }.getType());

            //使用编辑距离算法进行计算
            long distance = AlgorithmUtils.minDistance(loginUserTagList, userTagList);

            //把它放到List中
            list.add(new Pair<User,Long>(user,distance));
        }

        //按编辑距离从小到大排序
        List<Pair<User, Long>> topTagList = list.stream().sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .limit(num)
                .collect(Collectors.toList());

        //因为topTagList的存放的键是用户（User），但这里的User只有id和tags字段，所以这里将所有的User的id取出来
        List<Long> matchUserIdList = topTagList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());

        //因为我们第一步之查出id和tags的信息，所以现在查询对应id的用户的信息
        QueryWrapper<User> userVOQueryWrapper = new QueryWrapper<>();
        userVOQueryWrapper.in("id",matchUserIdList);

        //注意这里使用queryWrapper中的in进行查询，所查询来的用户的id没有根据topTagList中的user中的id进行排序
        //例如：matchUserIdList的user中的id = 1,3,2
        //而通过wrapper查询出来的user的id = 1,2,3
        //接下来的做法要使用map记录user的id和user的信息 像这样：1 => user1 2 => user2 3 => user3
        //再通过遍历matchUserIdList,get(id)取出对用用户信息
        Map<Long, List<User>> userIdUserListMap = this.list(userVOQueryWrapper)
                .stream()
                .collect(Collectors.groupingBy(User::getId));

        List<User> matchUserList = new ArrayList<>();
        for(Long id : matchUserIdList){
            matchUserList.add(userIdUserListMap.get(id).get(0));
        }

        //脱敏
        List<UserVO> matchUserVOList = new ArrayList<>();
        for (User user : matchUserList) {
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user,userVO);
            matchUserVOList.add(userVO);
        }

        return matchUserVOList;
    }

    /**
     * 展示我的好友
     * @param loginUser
     * @return
     */
    @Override
    public List<User> showMyFriends(User loginUser) {
        long userId = loginUser.getId();
        User curUser = this.getById(userId);
        Set<Long> friendsIds = JsonToStringUtils.stringJsonListToSet(curUser.getFriendsIds());
        List<User> userList = friendsIds.stream().map(user -> this.getSafeUser(this.getById(user))).collect(Collectors.toList());

        return userList;
    }

    /**
     * 判断该用户是否为我好友
     * @param fromId
     * @param loginUser
     * @return
     */
    @Override
    public boolean isMyFriend(Long fromId, User loginUser) {
        long userId = loginUser.getId();
        User user = this.getById(userId);
        String friendsIds = user.getFriendsIds();
        Set<Long> set = JsonToStringUtils.stringJsonListToSet(friendsIds);
        if(set.contains(fromId)){
            return true;
        }else{
            return false;
        }
    }

    /**
     * 添加标签
     * @param tags
     * @param loginUser
     * @return
     */
    @Override
    public boolean addTags(String[] tags, User loginUser) {
        //获取到用户原有的json格式的标签
        long userId = loginUser.getId();
        User user = this.getById(userId);
        String userTags = user.getTags();

        Set<String> set = JsonToStringUtils.stringTagsJsonListToSet(userTags);
        for (String tag : tags) {
            set.add(tag);
        }

        Gson gson = new Gson();
        String json = gson.toJson(set);
        LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.eq(User::getId,userId);
        lambdaUpdateWrapper.set(User::getTags,json);

        AtomicBoolean flag = new AtomicBoolean(false);

        flag.set(this.update(lambdaUpdateWrapper));

        return flag.get();
    }


    /**
     * 根据标签搜索用户【使用SQL的方式进行搜索查询】
     *
     * @param tagNameList：用户要拥有的标签
     * @return
     */
    @Deprecated//该注解表示此方法已过时
    private List<User> searchUserByTagsBySQL(List<String> tagNameList) {
        //判断是否为空
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorType.PARAMS_ERROR);
        }

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        for (String tagName : tagNameList) {
            queryWrapper = queryWrapper.like("tags", tagName);
        }

        List<User> userList = userMapper.selectList(queryWrapper);


        //利用函数式接口对userList进行脱敏，并返回。
        return userList.stream().map(this::getSafeUser).collect(Collectors.toList());
    }

}




